package net.oschina.bilbodai.common.beanutil.bean.core;

import net.oschina.bilbodai.common.beanutil.bean.annotation.DumpIgnore;
import net.oschina.bilbodai.common.beanutil.bean.annotation.DumpIgnores;
import net.oschina.bilbodai.common.beanutil.bean.annotation.DumpProperties;
import net.oschina.bilbodai.common.beanutil.bean.annotation.DumpProperty;
import net.oschina.bilbodai.common.beanutil.util.CollectionUtils;
import net.oschina.bilbodai.common.beanutil.util.TypeUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author daimao  Date: 16/5/28 Time: 下午1:32
 * @version $Id$
 */
public class PropertyInfoProcessor {

    private static final ProcessPredicate ACCEPT_ALL = new ProcessPredicate() {
        @Override
        public boolean accept(Field field, Class<?> type) {
            return true;
        }
    };

    public static final ProcessPredicate ACCEPT_GETTERS = new ProcessPredicate() {
        @Override
        public boolean accept(Field field, Class<?> type) {
            return TypeUtil.getGetterMethod(field.getName(), type) != null;
        }
    };

    public static final ProcessPredicate ACCEPT_SETTERS = new ProcessPredicate() {
        @Override
        public boolean accept(Field field, Class<?> type) {
            return TypeUtil.getSetterMethod(field.getName(), field.getType(), type) != null;
        }
    };

    public static List<PropertyInfo> process(Class<?> type, Class<?> peerType, ProcessPredicate predicate, String profile) {
        List<Field> allDeclaredFields = TypeUtil.getAllFields(type);
        List<PropertyInfo> propertyInfos = new LinkedList<>();
        for (Field field : allDeclaredFields) {
            if (field.getDeclaringClass().equals(Object.class))
                continue;
            if ((field.getModifiers() & Modifier.NATIVE) > 0)
                continue;
            if (!predicate.accept(field, type))
                continue;
            if (shouldIgnoreThisField(peerType, field, profile))
                continue;

            Set<PropertyInfo> infos = findPropertyInfo(type, peerType, field, profile);
            propertyInfos.addAll(infos);
        }

        return propertyInfos;
    }

    private static Set<PropertyInfo> findPropertyInfo(Class<?> ownerType, Class<?> peerType, Field field, String profile) {
        Set<PropertyInfo> propertyInfos = new LinkedHashSet<>();
        List<DumpProperty> dumpPropertyList = new LinkedList<>();
        DumpProperties dumpProperties = field.getAnnotation(DumpProperties.class);
        if (dumpProperties != null)
            dumpPropertyList.addAll(Arrays.asList(dumpProperties.value()));
        DumpProperty dumpProperty = field.getAnnotation(DumpProperty.class);
        if (dumpProperty != null)
            dumpPropertyList.add(dumpProperty);

        for (DumpProperty dp : dumpPropertyList) {
            String[] profiles = dp.profiles();
            if (CollectionUtils.isEmpty(profiles) || CollectionUtils.contains(profiles, profile)) {
                propertyInfos.add(new PropertyInfo(ownerType, field, dp));
            }
        }

        propertyInfos.add(new PropertyInfo(ownerType, field));
        return propertyInfos;
    }

    private static boolean shouldIgnoreThisField(Class<?> peerType, Field field, String profile) {
        boolean ignoreThisField = false;
        DumpIgnores dumpIgnores = field.getAnnotation(DumpIgnores.class);
        if (dumpIgnores != null) {
            DumpIgnore[] ignores = dumpIgnores.value();
            for (DumpIgnore ignore : ignores) {
                if (ignoreDump(ignore, peerType, profile)) {
                    ignoreThisField = true;
                    break;
                }
            }
        }
        if (!ignoreThisField) {
            DumpIgnore dumpIgnore = field.getAnnotation(DumpIgnore.class);
            if (ignoreDump(dumpIgnore, peerType, profile)) {
                ignoreThisField = true;
            }
        }
        return ignoreThisField;
    }

    private static boolean ignoreDump(DumpIgnore dumpIgnore, Class<?> peerType, String profile) {
        if (dumpIgnore == null)
            return false;
        if (CollectionUtils.isEmpty(dumpIgnore.profiles())) { //如果没指定,忽略所有的profile
            return true;
        }
        return CollectionUtils.contains(dumpIgnore.profiles(), profile);//如果为指定表示ignore所有关联的复制
    }

    public interface ProcessPredicate {
        boolean accept(Field field, Class<?> type);
    }
}